/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright (c) 2020 Nordic Semiconductor ASA
 * Copyright (c) 2020 Arm Limited
 */

#include <assert.h>
#include "bootutil/image.h"
#include "bootutil_priv.h"
#include "bootutil/boot_record.h"
#include "bootutil/bootutil.h"
#include "bootutil/bootutil_log.h"
#include "bootutil/bootutil_public.h"
#include "bootutil/fault_injection_hardening.h"

#include "mcuboot_config/mcuboot_config.h"

BOOT_LOG_MODULE_DECLARE(mcuboot);

/* Variables passed outside of unit via pointers. */
static struct image_header _hdr = { 0 };
static struct boot_loader_state state;

struct boot_loader_state *boot_get_loader_state(void)
{
    return &state;
}

#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO)
static struct image_max_size image_max_sizes[BOOT_IMAGE_NUMBER] = {0};

struct image_max_size *boot_get_image_max_sizes(void)
{
    return image_max_sizes;
}
#endif

#if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT) || defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE)
/**
 * Validate hash of a primary boot image.
 *
 * @param[in]	fa_p	flash area pointer
 * @param[in]	hdr	boot image header pointer
 *
 * @return		FIH_SUCCESS on success, error code otherwise
 */
fih_ret
boot_image_validate(const struct flash_area *fa_p,
                    struct image_header *hdr)
{
    static uint8_t tmpbuf[BOOT_TMPBUF_SZ];
    FIH_DECLARE(fih_rc, FIH_FAILURE);

    BOOT_LOG_DBG("boot_image_validate: encrypted == %d", (int)IS_ENCRYPTED(hdr));

    /* NOTE: The first argument to boot_image_validate, for enc_state pointer,
     * is allowed to be NULL only because the single image loader compiles
     * with BOOT_IMAGE_NUMBER == 1, which excludes the code that uses
     * the pointer from compilation.
     */
    /* Validate hash */
    if (IS_ENCRYPTED(hdr))
    {
        /* Clear the encrypted flag we didn't supply a key
         * This flag could be set if there was a decryption in place
         * was performed. We will try to validate the image, and if still
         * encrypted the validation will fail, and go in panic mode
         */
        hdr->ih_flags &= ~(ENCRYPTIONFLAGS);
    }
    FIH_CALL(bootutil_img_validate, fih_rc, NULL, hdr, fa_p, tmpbuf,
             BOOT_TMPBUF_SZ, NULL, 0, NULL);

    FIH_RET(fih_rc);
}
#endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT || MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE*/

#if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE)
inline static fih_ret
boot_image_validate_once(const struct flash_area *fa_p,
                    struct image_header *hdr)
{
    static struct boot_swap_state state;
    int rc;
    FIH_DECLARE(fih_rc, FIH_FAILURE);

    BOOT_LOG_DBG("boot_image_validate_once: flash area %p", fap_p);

    memset(&state, 0, sizeof(struct boot_swap_state));
    rc = boot_read_swap_state(fa_p, &state);
    if (rc != 0)
        FIH_RET(FIH_FAILURE);
    if (state.magic != BOOT_MAGIC_GOOD
            || state.image_ok != BOOT_FLAG_SET) {
        /* At least validate the image once */
        FIH_CALL(boot_image_validate, fih_rc, fa_p, hdr);
        if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
            FIH_RET(FIH_FAILURE);
        }
        if (state.magic != BOOT_MAGIC_GOOD) {
            rc = boot_write_magic(fa_p);
            if (rc != 0)
                FIH_RET(FIH_FAILURE);
        }
        rc = boot_write_image_ok(fa_p);
        if (rc != 0)
            FIH_RET(FIH_FAILURE);
    }
    FIH_RET(FIH_SUCCESS);
}
#endif

int
boot_open_all_flash_areas(struct boot_loader_state *state)
{
    int rc;

    rc = flash_area_open(FLASH_AREA_IMAGE_PRIMARY(0), &BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT));
    assert(rc == 0);

    return rc;
}

void
boot_close_all_flash_areas(struct boot_loader_state *state)
{
    flash_area_close(BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT));
}

#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO)
static int app_max_sectors(struct boot_loader_state *state)
{
    uint32_t sz = 0;
    uint32_t sector_sz;
    uint32_t trailer_sz;
    uint32_t trailer_sectors = 0;

    sector_sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0);
    trailer_sz = boot_trailer_sz(BOOT_WRITE_SZ(state));

    while (1) {
        sz += sector_sz;
        ++trailer_sectors;

        if  (sz >= trailer_sz) {
            break;
        }
    }

    return boot_img_num_sectors(state, BOOT_PRIMARY_SLOT) - trailer_sectors;
}

int app_max_size(struct boot_loader_state *state)
{
    uint32_t sector_sz;

    sector_sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0);

    return app_max_sectors(state) * sector_sz;
}

/**
 * Fetches the maximum allowed size of the image
 */
const struct image_max_size *boot_get_max_app_size(void)
{
    if (image_max_sizes[0].calculated == false) {
        /* Information not available, need to fetch it */
        boot_fetch_slot_state_sizes();
    }

    return image_max_sizes;
}
#endif

/**
 * Gather information on image and prepare for booting.
 *
 * @parami[out]	rsp	Parameters for booting image, on success
 *
 * @return		FIH_SUCCESS on success; nonzero on failure.
 */
fih_ret
boot_go(struct boot_rsp *rsp)
{
    int rc = -1;
    FIH_DECLARE(fih_rc, FIH_FAILURE);

    BOOT_LOG_DBG("boot_go: Single loader");

    rc = boot_open_all_flash_areas(&state);

    rc = boot_image_load_header(BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT), &_hdr);
    if (rc != 0)
        goto out;

#ifdef MCUBOOT_RAM_LOAD
        state.imgs[0][0].hdr = _hdr;

        rc = boot_load_image_to_sram(&state);
        if (rc != 0)
            goto out;
#endif

#ifdef MCUBOOT_VALIDATE_PRIMARY_SLOT
    FIH_CALL(boot_image_validate, fih_rc, BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT), &_hdr);
    if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
#ifdef MCUBOOT_RAM_LOAD
        boot_remove_image_from_sram(&state);
#endif
        goto out;
    }
#elif defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE)
    FIH_CALL(boot_image_validate_once, fih_rc, BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT), &_hdr);
    if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
#ifdef MCUBOOT_RAM_LOAD
        boot_remove_image_from_sram(&state);
#endif
        goto out;
    }
#else
    fih_rc = FIH_SUCCESS;
#endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT */

#ifdef MCUBOOT_MEASURED_BOOT
    rc = boot_save_boot_status(0, &_hdr, BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT));
    if (rc != 0) {
        BOOT_LOG_ERR("Failed to add image data to shared area");
        return rc;
    }
#endif /* MCUBOOT_MEASURED_BOOT */

#ifdef MCUBOOT_DATA_SHARING
    rc = boot_save_shared_data(&_hdr, BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT), 0, NULL);
    if (rc != 0) {
        BOOT_LOG_ERR("Failed to add data to shared memory area.");
        return rc;
    }
#endif /* MCUBOOT_DATA_SHARING */

    rsp->br_flash_dev_id = flash_area_get_device_id(BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT));
    rsp->br_image_off = flash_area_get_off(BOOT_IMG_AREA(&state, BOOT_PRIMARY_SLOT));
    rsp->br_hdr = &_hdr;

out:
    boot_close_all_flash_areas(&state);

    FIH_RET(fih_rc);
}
